package org.jjliu.example.dao;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.HibernateException;
import org.jjliu.example.dao.Filter.Operator;

import freemarker.core.ParseException;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;

public class HqlModel implements Cloneable{
	private String selectColumns;
	private boolean distinct;
	private String fromTable;
	private String whereClause;
	private Map<String,Object> whereParams;
	private String groupBy;
	private String having;
	private String orderBy;
	
	private boolean builded;
	private String buildHql;
	private Map<String,Object> buildMap = new HashMap<String,Object>();

	/** 筛选 */
	private List<Filter> filters;

	/** 排序 */
	private List<Order> orders;

	@Override
	protected Object clone() throws CloneNotSupportedException {
		HqlModel hqlModel = (HqlModel) super.clone();
		hqlModel.whereParams = new HashMap<String, Object>();
		hqlModel.whereParams.putAll(whereParams);
		hqlModel.filters = new ArrayList<Filter>();
		hqlModel.filters.addAll(filters);
		hqlModel.orders = new ArrayList<Order>();
		hqlModel.orders.addAll(orders);
		hqlModel.buildMap = new HashMap<String, Object>();
		hqlModel.buildMap.putAll(buildMap);
		return hqlModel;
	}
	
	
	
	class HqlKeyword{
		boolean find;
		char[] temp;
		int start;
		int end;
		public HqlKeyword(String temp) {
			this.temp = temp.toCharArray();
		}
		public boolean match(String wordstr,int offset){
			if(wordstr.length() < offset+temp.length) return false;
			int _offset = offset;
			for(char t : temp){
				char w = wordstr.charAt(_offset++);
				if(w != t && w != (t-32)){
					return false;
				}
			}
			start = offset;
			end = offset + temp.length;
			find = true;
			return true;
		}
		public int size(){
			return temp.length;
		}
	}
	
	private void divide(String priHql){
		if(StringUtils.isBlank(priHql))return;
		String hql = priHql;

		HqlKeyword curKeyword = null;
		HqlKeyword select = new HqlKeyword("select");
		HqlKeyword distinct = new HqlKeyword("distinct");
		HqlKeyword from = new HqlKeyword("from");
		HqlKeyword where = new HqlKeyword("where");
		HqlKeyword group_by = new HqlKeyword("group by");
		HqlKeyword having_ = new HqlKeyword("having");
		HqlKeyword order_by = new HqlKeyword("order by");
		ArrayList<HqlKeyword> keys = new ArrayList<HqlModel.HqlKeyword>();
		
		int leftbracket = 0;
		for(int i=0;i<hql.length();){
			char c= hql.charAt(i);
			if(c=='('){
				leftbracket++;
				i++;
			}else if(c==')'){
				leftbracket--;
				i++;
			}else if(leftbracket==0){
				if(curKeyword==null){
					if(select.match(hql, i)){
						curKeyword=select;
						i += curKeyword.size();
						keys.add(curKeyword);
						continue;
					}
				}
				if(curKeyword==select){
					if(distinct.match(hql, i)){
						curKeyword=distinct;
						i += curKeyword.size();
						keys.add(curKeyword);
						continue;
					}
				}
				if(curKeyword==null || curKeyword==select){
					if(from.match(hql, i)){
						curKeyword=from;
						i += curKeyword.size();
						keys.add(curKeyword);
						continue;
					}
				}
				if(curKeyword==from){
					if(where.match(hql, i)){
						curKeyword=where;
						i += curKeyword.size();
						keys.add(curKeyword);
						continue;
					}
				}
				if(curKeyword==from || curKeyword==where){
					if(group_by.match(hql, i)){
						curKeyword=group_by;
						i += curKeyword.size();
						keys.add(curKeyword);
						continue;
					}
				}
				if(curKeyword==group_by){
					if(having_.match(hql, i)){
						curKeyword=having_;
						i += curKeyword.size();
						keys.add(curKeyword);
						continue;
					}
				}
				if(curKeyword==from ||curKeyword==where || curKeyword==group_by || curKeyword==having_){
					if(order_by.match(hql, i)){
						curKeyword=order_by;
						i += curKeyword.size();
						keys.add(curKeyword);
						continue;
					}
				}
				i++;
			}else{
				i++;
			}
		}
		if(!from.find){
			throw new HibernateException("can't parse:"+hql);
		}
		int keysLen=keys.size();
		for(int i=0; i<keysLen; i++){
			curKeyword = keys.get(i);
			if(curKeyword==select){
				if(i == keysLen-1){
					selectColumns = hql.substring(curKeyword.end);
				}else{
					selectColumns = hql.substring(curKeyword.end,keys.get(i+1).start);
				}
			}else if(curKeyword==from){
				if(i == keysLen-1){
					fromTable = hql.substring(curKeyword.end);
				}else{
					fromTable = hql.substring(curKeyword.end,keys.get(i+1).start);
				}
			}else if(curKeyword==where){
				if(i == keysLen-1){
					whereClause = hql.substring(curKeyword.end);
				}else{
					whereClause = hql.substring(curKeyword.end,keys.get(i+1).start);
				}
			}else if(curKeyword==group_by){
				if(i == keysLen-1){
					groupBy = hql.substring(curKeyword.end);
				}else{
					groupBy = hql.substring(curKeyword.end,keys.get(i+1).start);
				}
			}else if(curKeyword==having_){
				if(i == keysLen-1){
					having = hql.substring(curKeyword.end);
				}else{
					having = hql.substring(curKeyword.end,keys.get(i+1).start);
				}
			}else if(curKeyword==order_by){
				if(i == keysLen-1){
					orderBy = hql.substring(curKeyword.end);
				}else{
					orderBy = hql.substring(curKeyword.end,keys.get(i+1).start);
				}
			}
		}
	}
	
	
	private String escape(String property){
		String _property = property.replaceAll("\\.", "__");
		return _property;
	}
	
	public void build(){
		if(builded)return;
		buildHql=null;
		buildMap.clear();
		if(MapUtils.isNotEmpty(this.getWhereParams())){
			buildMap.putAll(this.getWhereParams());
		}
		StringBuffer sb = new StringBuffer();
		if(StringUtils.isNotEmpty(this.getSelectColumns())){
			sb.append("select ");
			if(this.isDistinct()){
				sb.append(" distinct ");
			}
			sb.append(this.getSelectColumns());
		}
		if(StringUtils.isNotEmpty(this.getFromTable())){
			sb.append(" from ").append(this.getFromTable());
		}
		if(StringUtils.isNotEmpty(this.getWhereClause())){
			sb.append(" where ").append(this.getWhereClause());
		}
		if(CollectionUtils.isNotEmpty(this.getFilters())){
			if(StringUtils.isEmpty(this.getWhereClause())){
				sb.append(" where ");
			}else{
				sb.append(" and ");
			}
			for(int i=0;i<this.getFilters().size();i++){
				if(i!=0){
					sb.append(" and ");
				}
				Filter filter = this.getFilters().get(i);
				Operator op = filter.getOperator();
				String property = filter.getProperty();
				Object value = filter.getValue();
				
				if(value != null){
					String _property = escape(property);
					if (op == Operator.eq) {
						sb.append(property).append(" = :").append(_property);
						buildMap.put(_property, value);
					} else if (op == Operator.ne) {
						sb.append(property).append(" != :").append(_property);
						buildMap.put(_property, value);
					} else if (op == Operator.gt) {
						sb.append(property).append(" > :").append(_property);
						buildMap.put(_property, value);
					} else if (op == Operator.lt) {
						sb.append(property).append(" < :").append(_property);
						buildMap.put(_property, value);
					} else if (op == Operator.ge) {
						sb.append(property).append(" >= :").append(_property);
						buildMap.put(_property, value);
					} else if (op == Operator.le) {
						sb.append(property).append(" <= :").append(_property);
						buildMap.put(_property, value);
					} else if (op == Operator.like) {
						sb.append(property).append(" like :").append(_property);
						String _value = (String)value;
						_value = (StringUtils.contains(_value, '%'))?_value:("%"+_value+"%");
						buildMap.put(_property, _value);
					} else if (op == Operator.in) {
						sb.append(property).append(" in (:").append(_property).append(")");
						buildMap.put(_property, value);
					}else{
						throw new HibernateException("can't parse:"+filter.toString());
					}
				}else{
					if (op == Operator.isNull) {
						sb.append(property).append(" is null");
					} else if (op == Operator.isNotNull) {
						sb.append(property).append(" is not null");
					}else{
						throw new HibernateException("can't parse:"+filter.toString());
					}
				}
					
			}
		}
		
		if(StringUtils.isNotEmpty(this.getGroupBy())){
			sb.append(" group by ").append(this.getGroupBy());
		}
	
		if(StringUtils.isNotEmpty(this.getHaving())){
			sb.append(" having ").append(this.getHaving());
		}
		
		if(StringUtils.isNotEmpty(this.getOrderBy())){
			sb.append(" order by ").append(this.getOrderBy());
		}
		
		if(CollectionUtils.isNotEmpty(this.getOrders())){
			if(StringUtils.isEmpty(this.getOrderBy())){
				sb.append(" order by ");
			}else{
				sb.append(" ");
			}
			for(int i=0;i<this.getOrders().size();i++){
				Order order = this.getOrders().get(i);
				if(i!=0){
					sb.append(",");
				}
				sb.append(order.getProperty()).append(" ").append(order.getDirection().toString());
			}
		}
		buildHql = sb.toString();
	}
	
	
	public HqlModel() {
		buildMap = new HashMap<String,Object>();
		whereParams = new HashMap<String, Object>();
		filters = new ArrayList<Filter>();
		orders = new ArrayList<Order>();
	}
	public HqlModel(String hql) {
		buildMap = new HashMap<String,Object>();
		whereParams = new HashMap<String, Object>();
		filters = new ArrayList<Filter>();
		orders = new ArrayList<Order>();
		divide(hql);
	}
	public HqlModel(TempParser parser) {
		buildMap = new HashMap<String,Object>();
		whereParams = new HashMap<String, Object>();
		filters = new ArrayList<Filter>();
		orders = new ArrayList<Order>();
		try {
			String priHql=parser.parse();
			divide(priHql);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public String getSelectColumns() {
		return selectColumns;
	}
	public HqlModel setSelectColumns(String selectColumns) {
		builded=false;
		this.selectColumns = selectColumns;
		return this;
	}

	public boolean isDistinct() {
		return distinct;
	}


	public HqlModel setDistinct(boolean distinct) {
		this.distinct = distinct;
		return this;
	}
	public String getFromTable() {
		return fromTable;
	}
	public HqlModel setFromTable(String fromTable) {
		builded=false;
		this.fromTable = fromTable;
		return this;
	}
	public String getWhereClause() {
		return whereClause;
	}
	public HqlModel setWhereClause(String whereClause) {
		builded=false;
		this.whereClause = whereClause;
		return this;
	}
	public Map<String, Object> getWhereParams() {
		return whereParams;
	}
	public HqlModel setWhereParams(Map<String, Object> whereParams) {
		this.whereParams = whereParams;
		return this;
	}

	public HqlModel addWhereParam(String key, Object value) {
		this.whereParams.put(key, value);
		return this;
	}

	public HqlModel addWhereParams(Map<String,?>whereParams) {
		this.whereParams.putAll(whereParams);
		return this;
	}

	public String getGroupBy() {
		return groupBy;
	}

	public HqlModel setGroupBy(String groupBy) {
		builded=false;
		this.groupBy = groupBy;
		return this;
	}

	public String getHaving() {
		return having;
	}

	public HqlModel setHaving(String having) {
		builded=false;
		this.having = having;
		return this;
	}
	
	public String getOrderBy() {
		return orderBy;
	}


	public void setOrderBy(String orderBy) {
		builded=false;
		this.orderBy = orderBy;
	}

	/**
	 * 获取筛选
	 * 
	 * @return 筛选
	 */
	public List<Filter> getFilters() {
		return filters;
	}

	/**
	 * 设置筛选
	 * 
	 * @param filters
	 *            筛选
	 */
	public HqlModel setFilters(List<Filter> filters) {
		builded=false;
		this.filters = filters;
		return this;
	}

	/**
	 * 获取排序
	 * 
	 * @return 排序
	 */
	public List<Order> getOrders() {
		return orders;
	}

	/**
	 * 设置排序
	 * 
	 * @param orders
	 *            排序
	 */
	public HqlModel setOrders(List<Order> orders) {
		builded=false;
		this.orders = orders;
		return this;
	}

	/**
	 * 添加筛选
	 * @param filter
	 * @return
	 */
	public HqlModel addFilter(Filter filter){
		builded=false;
		filters.add(filter);
		return this;
	}
	/**
	 * 添加排序
	 * @param order
	 * @return
	 */
	public HqlModel addOrder(Order order){
		builded=false;
		orders.add(order);
		return this;
	}

	public String getBuildHql() {
		return buildHql;
	}


	public Map<String, Object> getBuildMap() {
		return buildMap;
	}


	@Override
	public String toString() {
		return "HqlModel [selectColumns=" + selectColumns + ", distinct="
				+ distinct + ", fromTable=" + fromTable + ", whereClause="
				+ whereClause + ", whereParams=" + whereParams + ", groupBy="
				+ groupBy + ", having=" + having + ", orderBy=" + orderBy
				+ ", builded=" + builded + ", buildHql=" + buildHql
				+ ", buildMap=" + buildMap + ", filters=" + filters
				+ ", orders=" + orders + "]";
	}




	
	
	
}
